package agent

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"
	"time"

	agentv1beta1 "gopkg.openfuyao.cn/bkecommon/agent/api/v1beta1"
	configinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	"gopkg.openfuyao.cn/bkecommon/ntp"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	yaml2 "sigs.k8s.io/yaml"

	"gopkg.openfuyao.cn/bkeadm/pkg/executor/k8s"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/root"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

var (
	annotationKey   = "bkeagent.bocloud.com/create"
	annotationValue = "bke"
)

type Options struct {
	root.Options
	Args    []string `json:"args"`
	Name    string   `json:"name"`
	Command string   `json:"command"`
	File    string   `json:"file"`
	Nodes   string   `json:"nodes"`
}

var gvr = schema.GroupVersionResource{
	Group:    agentv1beta1.GVK.Group,
	Version:  agentv1beta1.GVK.Version,
	Resource: "commands",
}

func (op *Options) ClusterPre() (err error) {
	// k8s client
	if global.K8s == nil {
		global.K8s, err = k8s.NewKubernetesClient(op.KubeConfig)
		if err != nil {
			return err
		}
	}
	return nil
}

func (op *Options) Exec() {
	cmd := agentv1beta1.Command{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				annotationKey: annotationValue,
			},
		},
		Spec: agentv1beta1.CommandSpec{
			Suspend:  false,
			Commands: []agentv1beta1.ExecCommand{},
		},
	}
	cmd.SetGroupVersionKind(schema.GroupVersionKind{
		Group:   agentv1beta1.GVK.Group,
		Version: agentv1beta1.GVK.Version,
		Kind:    "Command",
	})
	cmd.SetName(op.Name)
	cmd.SetNamespace(metav1.NamespaceDefault)

	nodeMap := map[string]string{}
	for _, value := range strings.Split(op.Nodes, ",") {
		nodeMap[value] = value
	}
	cmd.Spec.NodeSelector = &metav1.LabelSelector{
		MatchLabels: nodeMap,
	}

	if op.Command != "" {
		cmd.Spec.Commands = append(cmd.Spec.Commands, agentv1beta1.ExecCommand{
			ID:            "command",
			Command:       []string{op.Command},
			Type:          agentv1beta1.CommandShell,
			BackoffIgnore: false,
			BackoffDelay:  0,
		})
	}

	if op.File != "" {
		b1, err := os.ReadFile(op.File)
		if err != nil {
			log.Error(err.Error())
			return
		}
		cm := corev1.ConfigMap{
			TypeMeta: metav1.TypeMeta{
				APIVersion: "v1",
				Kind:       "ConfigMap",
			},
			ObjectMeta: metav1.ObjectMeta{
				Name: op.Name,
			},
			Data: map[string]string{
				"value": string(b1),
			},
		}
		_, err = global.K8s.GetClient().CoreV1().ConfigMaps(metav1.NamespaceDefault).Create(context.Background(), &cm, metav1.CreateOptions{})
		if err != nil {
			log.Error(err.Error())
			return
		}
		cmd.Spec.Commands = append(cmd.Spec.Commands, agentv1beta1.ExecCommand{
			ID:            "file",
			Command:       []string{fmt.Sprintf("configmap:%s/%s:rx:shell", metav1.NamespaceDefault, op.Name)},
			Type:          agentv1beta1.CommandKubernetes,
			BackoffIgnore: false,
			BackoffDelay:  0,
		})
	}

	by, err := yaml2.Marshal(cmd)
	if err != nil {
		log.Error(err.Error())
		return
	}

	cmdName := fmt.Sprintf("%s.yaml", cmd.Name)
	err = os.WriteFile(cmdName, by, 0644)
	if err != nil {
		log.Error(fmt.Sprintf("File generation failure %s", err.Error()))
		return
	}

	err = global.K8s.InstallYaml(cmdName, map[string]string{}, "")
	if err != nil {
		log.Error(err.Error())
		return
	}
	log.BKEFormat(log.NIL, fmt.Sprintf("The execution command has been sent to the cluster, Please run the `bke command info "+
		"%s/%s` to optain the execution result", metav1.NamespaceDefault, op.Name))
}

func (op *Options) List() {
	var workloadUnstructured *unstructured.UnstructuredList
	var err error
	dynamicClient := global.K8s.GetDynamicClient()
	workloadUnstructured, err = dynamicClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		log.Error(err.Error())
		return
	}

	commandList := &agentv1beta1.CommandList{}
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(workloadUnstructured.UnstructuredContent(), commandList)
	if err != nil {
		log.Error(err.Error())
		return
	}
	const tabPadding = 2 // 列之间的最小空格数，用于tabwriter对齐
	w := tabwriter.NewWriter(os.Stdout, 0, 0, tabPadding, ' ', 0)
	fmt.Fprintln(w, "name\tsuspend\tnode\tLastStartTime\tCompletionTime\tPhase\tStatus")
	for _, bc := range commandList.Items {
		for node, v := range bc.Status {
			completionTime := ""
			if v.CompletionTime != nil {
				completionTime = v.CompletionTime.String()
			}
			fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
				bc.Namespace, bc.Name,
				strconv.FormatBool(bc.Spec.Suspend),
				node,
				v.LastStartTime.String(),
				completionTime,
				string(v.Phase),
				string(v.Status),
			)
		}
	}
	err = w.Flush()
	if err != nil {
		fmt.Println("flush tablewriter failed:", err.Error())
	}
}

func (op *Options) Info() {
	ns := strings.Split(op.Args[0], "/")
	var workloadUnstructured *unstructured.Unstructured
	var err error
	dynamicClient := global.K8s.GetDynamicClient()

	workloadUnstructured, err = dynamicClient.Resource(gvr).Namespace(ns[0]).Get(context.TODO(), ns[1], metav1.GetOptions{})
	if err != nil {
		log.Error(err.Error())
		return
	}
	commands := &agentv1beta1.Command{}
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(workloadUnstructured.UnstructuredContent(), commands)
	if err != nil {
		log.Error(err.Error())
		return
	}

	const tabPadding = 2 // 列之间的最小空格数，用于tabwriter对齐
	for node, v := range commands.Status {
		for _, c := range v.Conditions {
			w := tabwriter.NewWriter(os.Stdout, 0, 0, tabPadding, ' ', 0)
			fmt.Fprintln(w, "name\tsuspend\tnode\tID\tSTATUS")
			fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\t%s\n",
				commands.Namespace, commands.Name,
				strconv.FormatBool(commands.Spec.Suspend),
				node,
				c.ID,
				string(c.Status),
			)
			err = w.Flush()
			if err != nil {
				fmt.Println("flush tablewriter failed:", err.Error())
			}

			output := ""
			if len(c.StdOut) > 0 {
				output = strings.Join(c.StdOut, "")
			}
			if len(c.StdErr) > 0 {
				output = strings.Join(c.StdErr, "")
			}
			fmt.Println(output)
		}
	}
}

func (op *Options) Remove() {
	ns := strings.Split(op.Args[0], "/")
	var err error
	dynamicClient := global.K8s.GetDynamicClient()
	err = dynamicClient.Resource(gvr).Namespace(ns[0]).Delete(context.TODO(), ns[1], metav1.DeleteOptions{})
	if err != nil {
		log.Error(err.Error())
		return
	}
}

func (op *Options) SyncTime() {
	ntpServer := configinit.DefaultNTPServer
	if len(op.Args) >= 1 {
		ntpServer = op.Args[0]
	}
	err := ntp.Date(ntpServer)
	if err != nil {
		for i := 0; i < 2; i++ {
			time.Sleep(2 * time.Second)
			err = ntp.Date(ntpServer)
			if err == nil {
				return
			}
		}
		fmt.Println(fmt.Sprintf("Failed to connect to the ntp server %s", ntpServer))
	}
	return
}
